﻿using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Windows.Forms;
using RaGE.Properties;

namespace RaGE
{
    public class EListCollection
    {
        public short Version;
        public short Signature;
        public int ConversationListIndex;
        public string ConfigFile;
        public EList[] Lists;

        public void RemoveItem(int listIndex, int elementIndex)
        {
            Lists[listIndex].RemoveItem(elementIndex);
        }

        public void AddItem(int listIndex, object[] itemValues)
        {
            Lists[listIndex].AddItem(itemValues);
        }

        public string GetOffset(int listIndex)
        {
            return BitConverter.ToString(Lists[listIndex].listOffset);
        }

        public void SetOffset(int listIndex, string offset)
        {
            if (offset != "")
            {
                var array = offset.Split(new[] {'-'});

                Lists[listIndex].listOffset = new byte[array.Length];

                for (var i = 0; i < array.Length; i++)
                    Lists[listIndex].listOffset[i] = Convert.ToByte(array[i], 16);
            }
            else
                Lists[listIndex].listOffset = new byte[0];
        }

        public string GetValue(int listIndex, int elementIndex, int fieldIndex)
        {
            return Lists[listIndex].GetValue(elementIndex, fieldIndex);
        }

        public void SetValue(int listIndex, int elementIndex, int fieldIndex, string value)
        {
            Lists[listIndex].SetValue(elementIndex, fieldIndex, value);
        }

        public string GetType(int listIndex, int fieldIndex)
        {
            return Lists[listIndex].GetType(fieldIndex);
        }

        private static object ReadValue(BinaryReader br, string type)
        {
            object result;
            switch (type)
            {
                case "int16":
                    result = br.ReadInt16();
                    break;
                case "int32":
                    result = br.ReadInt32();
                    break;
                case "int64":
                    result = br.ReadInt64();
                    break;
                case "float":
                    result = br.ReadSingle();
                    break;
                case "double":
                    result = br.ReadDouble();
                    break;
                default:
                    if (type.Contains("byte:")) result = br.ReadBytes(Convert.ToInt32(type.Substring(5)));

                    else if (type.Contains("wstring:")) result = br.ReadBytes(Convert.ToInt32(type.Substring(8)));

                    else result = type.Contains("string:") ? br.ReadBytes(Convert.ToInt32(type.Substring(7))) : null;

                    break;
            }
            return result;
        }

        private static void WriteValue(BinaryWriter bw, object value, string type)
        {
            switch (type)
            {
                case "int16":
                    bw.Write((short)value);
                    break;
                case "int32":
                    bw.Write((int)value);
                    break;
                case "int64":
                    bw.Write((int)value);
                    break;
                case "float":
                    bw.Write((float)value);
                    break;
                case "double":
                    bw.Write((double)value);
                    break;
                default:
                    if (type.Contains("byte:")) bw.Write((byte[]) value);
                    
                    else if (type.Contains("wstring:")) bw.Write((byte[]) value);

                    else if (type.Contains("string:")) bw.Write((byte[]) value);

                    break;
            }
        }

        private static Hashtable LoadRules(string file)
        {
            var streamReader = new StreamReader(file);
            var hashtable = new Hashtable();

            while (true)
            {
                var text = streamReader.ReadLine();

                if (text == null) break;

                Application.DoEvents();

                if (text == "" || text.StartsWith("#")) continue;

                string key;
                string value;

                if (text.Contains("|"))
                {
                    key = text.Split(new[] { '|' })[0];
                    value = text.Split(new[] { '|' })[1];
                }
                else
                {
                    key = text;
                    value = "";
                }

                hashtable.Add(key, value);
            }
            streamReader.Close();
            return hashtable;
        }

        public void Save(string elFile)
        {
            if (File.Exists(elFile)) File.Delete(elFile);
            
            var fileStream = new FileStream(elFile, FileMode.Create, FileAccess.Write);
            var binaryWriter = new BinaryWriter(fileStream);

            for (var i = 0; i < Lists.Length; i++)
            {
                Application.DoEvents();

                if (Lists[i].listOffset.Length > 0) binaryWriter.Write(Lists[i].listOffset);

                if (i != ConversationListIndex) binaryWriter.Write(Lists[i].elementValues.Length);

                foreach (var t in Lists[i].elementValues)
                    for (var k = 0; k < t.Length; k++)
                        WriteValue(binaryWriter, t[k], Lists[i].elementTypes[k]);
            }

            binaryWriter.Close();
            fileStream.Close();
        }

        public void Export(string rulesFile, string targetFile)
        {
            var hashtable = LoadRules(rulesFile);

            if (File.Exists(targetFile)) File.Delete(targetFile);

            var fileStream = new FileStream(targetFile, FileMode.Create, FileAccess.Write);
            var binaryWriter = new BinaryWriter(fileStream);

            for (var i = 0; i < Lists.Length; i++)
            {
                if (hashtable.ContainsKey("REMOVELIST:" + i)) continue;

                if (hashtable.ContainsKey("REPLACEOFFSET:" + i))
                {
                    var separator = new[] { '-', ' ' };
                    var array = ((string)hashtable["REPLACEOFFSET:" + i]).Split(separator);

                    if (array.Length > 1)
                    {
                        var array2 = new byte[array.Length];

                        for (var j = 0; j < array.Length; j++)
                            array2[j] = Convert.ToByte(array[j], 16);

                        if (array2.Length > 0)
                            binaryWriter.Write(array2);
                    }
                }
                else
                {
                    if (Lists[i].listOffset.Length > 0)
                        binaryWriter.Write(Lists[i].listOffset);
                }

                if (i != ConversationListIndex)
                    binaryWriter.Write(Lists[i].elementValues.Length);

                foreach (var t in Lists[i].elementValues)
                {
                    for (var l = 0; l < t.Length; l++)
                    {
                        Application.DoEvents();

                        if (!hashtable.ContainsKey("REMOVEVALUE:" + i + ":" + l))
                            WriteValue(binaryWriter, t[l], Lists[i].elementTypes[l]);
                    }
                }
            }

            binaryWriter.Close();
            fileStream.Close();
        }

        public EList[] Load(string configFile, string elFile)
        {
            ConfigFile = configFile.Substring(configFile.LastIndexOf("\\", StringComparison.Ordinal));
            var streamReader = new StreamReader(configFile);
            var array = new EList[Convert.ToInt32(streamReader.ReadLine())];

            ConversationListIndex = Convert.ToInt32(streamReader.ReadLine());

            for (var i = 0; i < array.Length; i++)
            {
                Application.DoEvents();
                string text;

                do text = streamReader.ReadLine(); while (text == "");

                array[i] = new EList();
                array[i].listName = text;
                array[i].listOffset = null;

                var text2 = streamReader.ReadLine();

                if (text2 != "AUTO")
                    array[i].listOffset = new byte[Convert.ToInt32(text2)];

                var separator = new[] { ';' };

                var readLine = streamReader.ReadLine();
                if (readLine != null)
                    array[i].elementFields = readLine.Split(separator);

                var separator2 = new[] { ';' };

                var line = streamReader.ReadLine();
                if (line != null)
                    array[i].elementTypes = line.Split(separator2);
            }

            streamReader.Close();
            var fileStream = File.OpenRead(elFile);
            var binaryReader = new BinaryReader(fileStream);

            for (var j = 0; j < array.Length; j++)
            {
                Application.DoEvents();

                if (array[j].listOffset != null)
                {
                    if (array[j].listOffset.Length > 0)
                        array[j].listOffset = binaryReader.ReadBytes(array[j].listOffset.Length);
                }
                else
                {
                    if (j == 20)
                    {
                        var array2 = binaryReader.ReadBytes(4);
                        var array3 = binaryReader.ReadBytes(4);
                        var array4 = binaryReader.ReadBytes(BitConverter.ToInt32(array3, 0));
                        var array5 = binaryReader.ReadBytes(4);

                        array[j].listOffset = new byte[array2.Length + array3.Length + array4.Length + array5.Length];

                        Array.Copy(array2, 0, array[j].listOffset, 0, array2.Length);
                        Array.Copy(array3, 0, array[j].listOffset, 4, array3.Length);
                        Array.Copy(array4, 0, array[j].listOffset, 8, array4.Length);
                        Array.Copy(array5, 0, array[j].listOffset, array4.Length + 8, array5.Length);
                    }

                    if (j == 100)
                    {
                        var array6 = binaryReader.ReadBytes(4);
                        var array7 = binaryReader.ReadBytes(4);
                        var array8 = binaryReader.ReadBytes(BitConverter.ToInt32(array7, 0));

                        array[j].listOffset = new byte[array6.Length + array7.Length + array8.Length];

                        Array.Copy(array6, 0, array[j].listOffset, 0, array6.Length);
                        Array.Copy(array7, 0, array[j].listOffset, 4, array7.Length);
                        Array.Copy(array8, 0, array[j].listOffset, 8, array8.Length);
                    }
                }

                if (j == ConversationListIndex)
                {
                    if (array[j].elementTypes[0].Contains("AUTO"))
                    {
                        var bytes = Encoding.GetEncoding("GBK").GetBytes("facedata\\");
                        var position = binaryReader.BaseStream.Position;
                        var num2 = -72 - bytes.Length;
                        var flag = true;

                        while (flag)
                        {
                            flag = false;

                            foreach (var t in bytes)
                            {
                                num2++;
                                if (binaryReader.ReadByte() == t) continue;

                                flag = true;
                                break;
                            }
                        }

                        binaryReader.BaseStream.Position = position;
                        array[j].elementTypes[0] = "byte:" + num2;
                    }

                    array[j].elementValues = new object[1][];
                    array[j].elementValues[0] = new object[array[j].elementTypes.Length];
                    array[j].elementValues[0][0] = ReadValue(binaryReader, array[j].elementTypes[0]);
                }
                else
                {
                    array[j].elementValues = new object[binaryReader.ReadInt32()][];

                    for (var l = 0; l < array[j].elementValues.Length; l++)
                    {
                        array[j].elementValues[l] = new object[array[j].elementTypes.Length];

                        for (var m = 0; m < array[j].elementValues[l].Length; m++)
                            array[j].elementValues[l][m] = ReadValue(binaryReader, array[j].elementTypes[m]);
                    }
                }
            }

            binaryReader.Close();
            fileStream.Close();
            return array;
        }

        public EListCollection(string configFile, string elFile)
        {
            Lists = Load(configFile, elFile);
        }

        public EListCollection(string elFile)
        {
            Lists = Load(elFile);
        }

        // returns an eList array with preconfigured fields from configuration file
        private EList[] LoadConfiguration(string file)
        {
            //ConfigFile = configFiles[0]->Substring(configFiles[0]->LastIndexOf("\\"));
            var sr = new StreamReader(file);
            var li = new EList[Convert.ToInt32(sr.ReadLine())];

            try
            {
                ConversationListIndex = Convert.ToInt32(sr.ReadLine());
            }
            catch
            {
                ConversationListIndex = 58;
            }

            for (var i = 0; i < li.Length; i++)
            {
                Application.DoEvents();

                string line;

                while ((line = sr.ReadLine()) == "")
                {
                }

                li[i] = new EList();
                li[i].listName = line;
                li[i].listOffset = null;
                var offset = sr.ReadLine();

                if (offset != "AUTO")
                    li[i].listOffset = new byte[Convert.ToInt32(offset)];

                var readLine = sr.ReadLine();
                if (readLine != null) li[i].elementFields = readLine.Split(new[] { ';' });

                var s = sr.ReadLine();
                if (s != null) li[i].elementTypes = s.Split(new[] { ';' });
            }
            sr.Close();

            return li;
        }

        private EList[] Load(string elFile)
        {
            var li = new EList[0];

            // open the element file
            var fs = File.OpenRead(elFile);
            var br = new BinaryReader(fs);

            Version = br.ReadInt16();
            Signature = br.ReadInt16();

            // check if a corresponding configuration file exists
            // original: ConfigFile = Application::StartupPath + "\\configs\\" + ((ToolStripMenuItem^)sender)->Text + ".cfg";
            var configFiles = Directory.GetFiles(Application.StartupPath + "\\configs", "PW_*_v" + Version + Resources.cfg);

            if (configFiles.Length > 0)
            {
                // configure an eList array with the configuration file
                li = LoadConfiguration(configFiles[0]);

                // read the element file
                for (var i = 0; i < li.Length; i++)
                {
                    Application.DoEvents();

                    // read offset
                    if (li[i].listOffset != null)
                    {
                        // offset > 0
                        if (li[i].listOffset.Length > 0)
                            li[i].listOffset = br.ReadBytes(li[i].listOffset.Length);
                    }
                        // autodetect offset (for list 20 & 100)
                    else
                    {
                        switch (i)
                        {
                            case 20:
                                {
                                    var head = br.ReadBytes(4);
                                    var count = br.ReadBytes(4);
                                    var body = br.ReadBytes(BitConverter.ToInt32(count, 0));
                                    var tail = br.ReadBytes(4);
                                    li[i].listOffset = new byte[head.Length + count.Length + body.Length + tail.Length];

                                    Array.Copy(head, 0, li[i].listOffset, 0, head.Length);
                                    Array.Copy(count, 0, li[i].listOffset, 4, count.Length);
                                    Array.Copy(body, 0, li[i].listOffset, 8, body.Length);
                                    Array.Copy(tail, 0, li[i].listOffset, 8 + body.Length, tail.Length);
                                }
                                break;

                            case 100:
                                {
                                    var head = br.ReadBytes(4);
                                    var count = br.ReadBytes(4);
                                    var body = br.ReadBytes(BitConverter.ToInt32(count, 0));
                                    li[i].listOffset = new byte[head.Length + count.Length + body.Length];

                                    Array.Copy(head, 0, li[i].listOffset, 0, head.Length);
                                    Array.Copy(count, 0, li[i].listOffset, 4, count.Length);
                                    Array.Copy(body, 0, li[i].listOffset, 8, body.Length);
                                }
                                break;
                        }
                    }

                    // read conversation list
                    if (i == ConversationListIndex)
                    {
                        // Auto detect only works for Perfect World elements.data !!!
                        if (li[i].elementTypes[0].Contains("AUTO"))
                        {
                            var pattern = (Encoding.GetEncoding("GBK")).GetBytes("facedata\\");
                            var sourcePosition = br.BaseStream.Position;
                            var listLength = -72 - pattern.Length;
                            var run = true;

                            while (run)
                            {
                                run = false;

                                foreach (var t in pattern)
                                {
                                    listLength++;

                                    if (br.ReadByte() == t) continue;

                                    run = true;
                                    break;
                                }
                            }

                            br.BaseStream.Position = sourcePosition;
                            li[i].elementTypes[0] = "byte:" + listLength;
                        }

                        li[i].elementValues = new object[1][];
                        li[i].elementValues[0] = new object[li[i].elementTypes.Length];
                        li[i].elementValues[0][0] = ReadValue(br, li[i].elementTypes[0]);
                    }
                        // read lists
                    else
                    {
                        li[i].elementValues = new object[br.ReadInt32()][];

                        // go through all elements of a list
                        for (var j = 0; j < li[i].elementValues.Length; j++)
                        {
                            li[i].elementValues[j] = new object[li[i].elementTypes.Length];

                            // go through all fields of an element
                            for (var k = 0; k < li[i].elementValues[j].Length; k++)
                            {
                                li[i].elementValues[j][k] = ReadValue(br, li[i].elementTypes[k]);
                            }
                        }
                    }
                }
            }
            else
                MessageBox.Show(Resources.NoCfgFound + Version +
                    Resources.Pattern + Resources.ConfigsPW + Version + Resources.cfg);

            br.Close();
            fs.Close();

            return li;
        }
    }
}
